package com.flow.service.provider.toleration.apply.service.impl;

import com.flow.framework.common.error.SystemErrorCode;
import com.flow.framework.common.exception.CheckedException;
import com.flow.framework.core.system.initialization.ProxyManager;
import com.flow.framework.core.system.initialization.ProxyService;
import com.flow.framework.lock.enumeration.ILockKeyEnum;
import com.flow.framework.lock.helper.LockHelper;
import com.flow.framework.persistence.helper.TransactionHelper;
import com.flow.service.facade.experiment.apply.module.service.IApplyModuleService;
import com.flow.service.facade.experiment.apply.pojo.dto.ApplyModuleDto;
import com.flow.service.facade.experiment.apply.pojo.vo.ApplyModuleVo;
import com.flow.service.facade.toleration.TolerationErrorCode;
import com.flow.service.provider.toleration.apply.convert.ApplyConverter;
import com.flow.service.provider.toleration.apply.pojo.dto.ApplyDto;
import com.flow.service.provider.toleration.apply.pojo.vo.ApplyVo;
import com.flow.service.provider.toleration.apply.service.IApplyService;
import com.flow.service.provider.toleration.apply.service.IResourceApplyService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import java.util.Collections;

/**
 * 申请业务服务实现
 *
 * @author luoguopiao
 * @version 0.0.1
 * @date 2023/5/13
 */
@Service
@Slf4j
@RequiredArgsConstructor
public class ApplyServiceImpl implements IApplyService {

    private static final ProxyService<String, IResourceApplyService> PROXY_MANAGER = ProxyManager.proxy(IResourceApplyService.class);

    private final IApplyModuleService applyModuleService;

    /**
     * 创建申请
     *
     * @param dto dto
     * @return
     */
    @Override
    public ApplyVo create(ApplyDto dto) {
        String resourceId = dto.getResourceId();
        String applyType = dto.getApplyType();
        IResourceApplyService resourceApplyService = PROXY_MANAGER.typeOptional(applyType).orElseThrow(() -> {
            log.error("can't find service. apply type : {}", applyType);
            return new CheckedException(TolerationErrorCode.COMPUTER_SERVICE_NOT_EXIST_ERROR);
        });
        ILockKeyEnum lockEnum = resourceApplyService.getLockEnum(applyType);
        if (null == lockEnum) {
            log.error("resource apply service lock enum is null. apply type : {}", applyType);
            throw new CheckedException(SystemErrorCode.UNEXPECTED_ERROR);
        }
        resourceApplyService.check(applyType, resourceId);
        ApplyModuleDto applyModuleDto = ApplyConverter.convertToModuleDto(dto);

        ApplyModuleVo applyModuleVo = TransactionHelper.distributedTransactionResultExec(() -> {

            // 在使用分布式事务场景下，尽量将远程调用放到分布式锁外，这样可以提高锁的使用效率
            LockHelper.voidLockExecute(null, lockEnum,
                    Collections.singletonList(resourceId), () -> {
                        resourceApplyService.check(applyType, resourceId);
                        TransactionHelper.localNewTransactionVoidExec(() -> resourceApplyService.updateResourceApplying(applyType, resourceId));
                    });

            // 如果生产者需要禁止分布式事务调用，则可以在ModuleService的接口上增加@ForbidDistributedTransaction注解，
            // 使用场景为调用三方在机构操作多个业务或接口且多个业务或接口无法撤销，此时应该禁止使用分布式事务
            // 如：调用某机构创建用户并发起该用户需要完成的业务，如果该机构产生的数据不支持撤销，此时就不可使用分布式事务
            return applyModuleService.create(applyModuleDto);
        });
        return ApplyConverter.convertToVo(applyModuleVo);
    }

    /**
     * 根据申请编号获取申请
     *
     * @param applyNo applyNo
     * @return
     */
    @Override
    public ApplyVo getByApplyNo(String applyNo) {
        ApplyModuleVo applyModuleVo = applyModuleService.getByApplyNo(applyNo, false);
        return ApplyConverter.convertToVo(applyModuleVo);
    }
}